/**
 * MIT License
 * 
 * Copyright (c) 2024 - present @ ebraid
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <types.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>


/**
 * @brief Calculate the digits of a decimal number
 * @param number  decimal number
 * @return digits of a decimal number
*/
uint32_t digits10(uint64_t number) 
{
    uint32_t digits = 1;
    uint64_t threshold = 10;

    while (number >= threshold) {
        digits++;
        threshold *= 10;
    }

    return digits;
}


/**
 * @brief Convert int64 type to a string type
 * @param number:  Number to be converted
 * @param str_out: Converted string output
 * @return none
*/
void int64_to_str(int64_t number, char* str_out)
{
    int text_len = 0;
    if(number > 0) {
        text_len = digits10(number);
    }
    else if(number < 0) {
        str_out[0] = '-';
        number = - number;
        text_len = digits10(number) + 1;
    }
    else {
        str_out[0] = '0';
        str_out[1] = 0;
        return ;
    }

    str_out[text_len] = 0;

    while(number) {
        text_len -- ;
        str_out[text_len] = (number%10) + 48;
        number /= 10;
    }
}


/**
 * @brief Convert uint64 type to a string type
 * @param number:  Number to be converted
 * @param str_out: Converted string output
 * @return none
*/
void uint64_to_str(uint64_t number, char* str_out)
{
    int text_len = 0;

    if(number > 0) {
        text_len = digits10(number);
    }
    else {
        str_out[0] = '0';
        str_out[1] = 0;
        return ;
    }

    str_out[text_len] = 0;

    while(number) {
        text_len -- ;
        str_out[text_len] = (number%10) + 48;
        number /= 10;
    }
}


/**
 * @brief Convert int64 type to a hex string type
 * @param number:  Number to be converted
 * @param str_out: Converted string output
 * @return none
*/
void int64_to_hexstr(int64_t number, char* str_out)
{
    int i = 0;

    while (number) {
        if (number < 0) {
            str_out[i++] = '-';
            number = -number;
        }
        str_out[i] = number % 16;
        if (str_out[i] > 9) {
            str_out[i] += 87;
            i++;
        }
        else {
            str_out[i] += 48;
            i++;
        }
        number /= 16;
    }

    str_out[i] = '\0';
    int left = (str_out[0] == '-') ? 1 : 0, right = i - 1;

    while (left < right) {
        char temp = str_out[left];
        str_out[left] = str_out[right];
        str_out[right] = temp;
        left++;
        right--;
    }
}


/**
 * @brief Convert uint64 type to a hex string type
 * @param number:  Number to be converted
 * @param str_out: Converted string output
 * @return none
*/
void uint64_to_hexstr(uint64_t number, char* str_out)
{
    int i = 0;

    while (number) {
        str_out[i] = number % 16;
        if (str_out[i] > 9) {
            str_out[i] += 87;
            i++;
        }
        else {
            str_out[i] += 48;
            i++;
        }
        number /= 16;
    }

    str_out[i] = '\0';
    int left = (str_out[0] == '-') ? 1 : 0, right = i - 1;
    
    while (left < right) {
        char temp = str_out[left];
        str_out[left] = str_out[right];
        str_out[right] = temp;
        left++;
        right--;
    }
}


/**
 * @brief vsnprintf, this function is used to convert the format string and the arguments to a string
 * @param buffer:  Buffer to store the converted string
 * @param count:   Buffer size
 * @param format:  Format string
 * @param va:      Arguments list
 * @return in:     The number of characters that would have been written had count been sufficiently large
 */
int vsnprintf(char *buffer, size_t count, const char *format, va_list va)
{
    size_t pos = 0;

    if (buffer == NULL || format == NULL) {
        return -1;
    }

    if (count == 0) {
        return -1;
    }

    while (*format != '\0' && pos < count - 1) {
        if (*format == '%' && *(format + 1) != '%') {
            char *s;
            int d;
            char ch;
            size_t x;
            void *p;

            switch (*(format + 1)) {
            case 'c':
                ch = va_arg(va, int);
                buffer[pos++] = ch; 
                break;

            case 's':
                s = va_arg(va, char *);
                while (*s !=  0 && pos < (count - 1)) {
                    buffer[pos++] = *s++;
                }
                break;

            case 'd':
                d = va_arg(va, int);
                int64_to_str(d, buffer + pos);
                pos += strlen(buffer + pos);
                break;

            case 'x':
                x = va_arg(va, size_t);
                int64_to_hexstr(x, buffer + pos);
                pos += strlen(buffer + pos);
                break;

            case 'p':
                p = va_arg(va, void *);
                uint64_to_hexstr((size_t)p, buffer + pos);
                pos += strlen(buffer + pos);
                break;

            default:
                buffer[pos++] = *format;
            }
            format += 2;
        }
        else if (*format == '%' && *(format + 1) == '%') {
            buffer[pos++] = '%';
            format += 2;
        }
        else {
            buffer[pos++] = *format;
            format++;
        }
    }

    buffer[pos] = '\0';

    if (pos >= (count - 1)) {
        return count;
    }

    return pos;
}
